﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using NHibernate;
using NHibernate.Linq;

namespace Mindfor.Web.Data
{
	/// <summary>
	/// Class for file data object.
	/// </summary>
	public class File : IdEntity, IFileDownload, IPostCommit
	{
		const int MaxDbFileSize = 1048576; // = 1Mb
		byte[] m_newHddBytes;
		Stream m_newHddStream;

		/// <summary>
		/// Gets or sets file name without extension.
		/// </summary>
		public virtual string Name { get; set; }

		/// <summary>
		/// Gets or sets file extension.
		/// </summary>
		public virtual string Extension { get; set; }

		/// <summary>
		/// Gets or sets parent folder. Current file in root if parent is null.
		/// </summary>
		public virtual Folder Folder { get; set; }

		/// <summary>
		/// Gets or sets gallery.
		/// </summary>
		public virtual Gallery Gallery { get; set; }

		/// <summary>
		/// Gets or sets data stored in database.
		/// </summary>
		public virtual byte[] Data { get; set; }

		/// <summary>
		/// Gets or sets HDD file id.
		/// </summary>
		protected virtual int? HddId { get; set; }

		/// <summary>
		/// Gets file size.
		/// </summary>
		public virtual int Size { get; private set; }

		/// <summary>
		/// Gets or sets loads counter state.
		/// </summary>
		public virtual int Loads { get; set; }

		/// <summary>
		/// Gets or sets creation date.
		/// </summary>
		public virtual DateTime CreateDate { get; set; }

		/// <summary>
		/// Gets or sets last update date.
		/// </summary>
		public virtual DateTime UpdateDate { get; set; }

		/// <summary>
		/// Gets or sets recycle date.
		/// </summary>
		public virtual DateTime? RecycleDate { get; set; }

		/// <summary>
		/// Initializes new instance.
		/// </summary>
		public File()
		{
			CreateDate = DateTime.UtcNow;
		}

		/// <summary>
		/// Initializes new instance.
		/// </summary>
		/// <param name="fullName">File name with extension.</param>
		public File(string fullName)
			: this()
		{
			int index = fullName.LastIndexOf('.');
			if (index > -1)
			{
				Name = fullName.Substring(0, index);
				Extension = fullName.Substring(index + 1);
			}
			else
			{
				Name = fullName;
				Extension = "";
			}
		}

		/// <summary>
		/// Sets file data.
		/// </summary>
		/// <param name="data">File data.</param>
		public virtual void SetData(byte[] data)
		{
			m_newHddStream = null;
			if (data.Length > MaxDbFileSize)
				m_newHddBytes = data;
			else
			{
				Data = data;
				m_newHddBytes = null;
			}
			Size = data.Length;
			UpdateDate = DateTime.UtcNow;			
		}

		/// <summary>
		/// Sets file data.
		/// </summary>
		/// <param name="stream">File data stream.</param>
		public virtual void SetData(Stream stream)
		{
			m_newHddBytes = null;
			if (stream.Length > MaxDbFileSize)
				m_newHddStream = stream;
			else
			{
				Data = new byte[stream.Length];
				stream.Read(Data, 0, (int)stream.Length);
			}

			Size = (int)stream.Length;
			UpdateDate = DateTime.UtcNow;
		}

		/// <summary>
		/// Returns file data stream.
		/// </summary>
		/// <returns>File data stream.</returns>
		public virtual Stream GetData()
		{
			if (m_newHddStream != null)
				return m_newHddStream;
			else if (m_newHddBytes != null)
			{
				MemoryStream ms = new MemoryStream(m_newHddBytes);
				ms.Seek(0, SeekOrigin.Begin);
				return ms;
			}
			else if (HddId != null)
			{
				FileInfo file = new FileInfo(GetFilePath());
				return file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
			}
			else if (Data != null)
			{
				MemoryStream ms = new MemoryStream(Data);
				ms.Seek(0, SeekOrigin.Begin);
				return ms;
			}

			return null;			
		}

		/// <summary>
		/// Saves file data to entity or HDD.
		/// </summary>
		private void SaveDataToHdd(ISession data)
		{
			// upload file data
			if (m_newHddBytes != null || m_newHddStream != null)
			{
				// get hdd id
				if (HddId == null)
					HddId = Id;
					
				// create folder
				string folder = HttpContext.Current.Server.MapPath(CmsConstants.FilesDirectoryPath);
				DirectoryInfo dir = new DirectoryInfo(folder);
				if (!dir.Exists)
					dir.Create();

				// save file
				FileInfo f = new FileInfo(GetFilePath());
				using (FileStream fs = f.Create())
				{
					if (m_newHddBytes != null)
						fs.Write(m_newHddBytes, 0, m_newHddBytes.Length);
					else
					{
						int read;
						int len = 1024;
						byte[] buf = new byte[len];
						do
						{
							read = m_newHddStream.Read(buf, 0, len);
							fs.Write(buf, 0, len);
						}
						while (read > 0);
					}
				}

				m_newHddBytes = null;
				m_newHddStream = null;
			}
		}

		/// <summary>
		/// Deletes HDD file if one exists.
		/// </summary>
		private void DeleteHddData()
		{
			// delete hh file
			if (HddId.HasValue)
			{
				FileInfo file = new FileInfo(GetFilePath());
				if (file.Exists)
				{
					try
					{
						file.Delete();
					}
					catch
					{
					}
				}

				HddId = null;
			}
		}

		/// <summary>
		/// Returns full file path or null if HddId is null.
		/// </summary>
		protected string GetFilePath()
		{
			if (HddId.HasValue)
			{
				string folder = HttpContext.Current.Server.MapPath(CmsConstants.FilesDirectoryPath);
				return Path.Combine(folder, HddId.ToString());
			}
			return null;
		}

		/// <summary>
		/// Sets file to be recycled in system time.
		/// </summary>
		public virtual void Recycle()
		{
			RecycleDate = DateTime.UtcNow;
		}

		/// <summary>
		/// Invoked after data item is inserted.
		/// </summary>
		public virtual void OnPostInsert(ISession data)
		{
			SaveDataToHdd(data);
		}

		/// <summary>
		/// Invoked after data item is updated.
		/// </summary>
		public virtual void OnPostUpdate(ISession data)
		{
			SaveDataToHdd(data);
		}

		/// <summary>
		/// Invoked after data item is deleted.
		/// </summary>
		public virtual void OnPostDelete(ISession data)
		{
			DeleteHddData();
		}
	}
}
